# coding:utf-8
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
'''
# 修改matplotlib的配置，详细说明见最后
font_options={'family':'monospace','weight':'bold','size':'12'}
plt.rc('font',**font_options)
'''


def testone():
    data = np.arange(10)
    plt.figure(3)  # int表示图几
    plt.plot(data)
    plt.show()

# testone()


# -------------Figure和Subplot------------
# matplotlib的图像都位于Figure对象中，可以使用plt.figure创建一个新的figure
def testfigure():
    fig = plt.figure()  # 可以加入figsize=(10,8)参数指定figure的大小
    ax1 = fig.add_subplot(2, 2, 1)
    # 同理
    ax2 = fig.add_subplot(2, 2, 2)
    ax3 = fig.add_subplot(2, 2, 3)
    # 如果此时执行一条绘图命令(如plt.plot()),就会在最近一个用过的subplot(如果没有则创建一个)上绘制，隐藏创建figure和subplot的过程
    plt.plot(np.random.randn(50).cumsum(), 'k--')  # k--表示黑色的虚线，字母是颜色，后面是线型
    # 由fig.add_subplot返回的对象是AxesSubplot对象，故可以直接调用它们的实例方法绘图
    ax1.hist(np.random.randn(100), bins=20, color='k', alpha=0.3)  # alpha是透明度
    ax2.scatter(np.arange(30), np.arange(30) + 3 * np.random.randn(30))
    # 创建包含subplot网格的figure是一个非常常见的任务，matplotlib有一个更为方便的方法plt.subplots
    # 可以创建一个新的Figure，并返回一个含有已创建的subplot对象的NumPy数组
    fig, axes = plt.subplots(2, 3)  # 生成一个2*3布局的画布
    print(axes, type(axes))
    '''
    subplots函数可以实现轻松的对axes数组进行索引，像一个二维数组一样，如axes[0,1]
    还可以通过sharex和sharey指定subplot应该具有相同的X轴或Y轴。
    subplots函数的参数：nrows--subplot的行数；ncols--subplots的列数。
    sharex--所有subplot应该使用相同的X轴刻度(调节xlim会影响所有subplot)，同理sharey
    subplot_kw--用于创建个subplot的关键字字典
    **fig_kw--创建figure时的其他关键字，如plt.subplots(2,2,figsize=(8,6))
    '''
    axes[0, 0].plot(np.random.randn(30).cumsum(), 'r--')
    axes[0, 1].plot(np.random.randn(30).cumsum(), 'b--')
    plt.show()
# testfigure()


# ----------subplot周围的间距-----------
'''
默认情况下，matplotlib会在subplot外围留下一定的边距，并在subplot之间留下一定的间距。
间距跟图像的高度和宽度有关，因此，如果你调整了图像大小(不管是编程还是手工)，间距也会自动调整。
'''


def subdistance():
    '''
    利用Figure的subplots_adjust方法修改间距。它是一个顶级函数
    subplots_adjust(left=None,bottom=None,right=None,top=None,wspace=None,hspace=None)
    wspace和hspace⽤于控制宽度和⾼度的百分⽐，可以⽤作subplot之间的间距。
    '''
    fig, axes = plt.subplots(2, 2, sharex=True, sharey=True)
    for i in range(2):
        for j in range(2):
            axes[i, j].hist(
                np.random.randn(500),
                bins=50, color='k',
                alpha=0.5)
    # 将间距收缩到0，这样轴标签就重叠了。matplotlib不会检查标签重叠，需自行设定刻度位置和刻度标签。
    plt.subplots_adjust(wspace=0, hspace=0)
    plt.show()
# subdistance()


# ---------------------颜色、标记和线型----------------------
'''
maplotlib的plot函数接受一组X和Y坐标，还可以接受一个表示颜色和线型的字符串缩写。
如ax.plot(x,y,'g--')根据x和y，画绿色虚线。一个字符串指定，非常方便。也可用更明确的方式 ax.plot(x,y,linestyle='--',color='g')
常用颜色可以是用颜色缩写，也可使用颜色代码(如'# CECECE')。在Ipython中可以使用plt.plot?查看所有的线型
线图用于标记强调数据点。matlotlib在连续作图时可能不太容易看清真实数据点。
标记可以放到格式字符串中，但标记类型和线型必须放在颜色后面。
'''


def testcolorline():
    plt.plot(np.random.randn(30).cumsum(), 'ko--')
    # 也可使用更明确的形式
    # plt.plot(np.random.randn(30).cumsum(),color='k',linestyle='dashed',marker='o')
    # 在线型图中，非实际数据点默认是按线型方式插值的。可以通过drawstyle选项修改
    data = np.random.randn(30).cumsum()
    plt.figure()  # 新建一个figure，否则会在最近的图上作图
    plt.plot(data, 'k--', label='Default')
    plt.plot(
        np.random.randn(30).cumsum(),
        'k-',
        drawstyle='steps-post',
        label='steps-post')
    plt.plot(
        np.random.randn(30).cumsum(),
        linestyle='--',
        color='g',
        marker='o')
    # 必须使用plt.legend()来创建图例，不论是否使用了label标签
    plt.legend(loc='best')
    plt.show()
# testcolorline()


# ------------------刻度、标签和图例------------------------
'''
对于大多数的图标装饰项，其主要实现方式有二：使用过程型的pyplot接口及更为面向对象的原生matplotlib API
pyplot接口的设计目的就是交互式标签，含有注入xlim、xticks和xticklabels之类的方法。他们分别控制图标的范围、刻度位置、刻度标签等。
其使用方式有以下两种：
调用时不带参数，则返回当前的参数值(例如，plt.xlim()返回当前的X轴绘图范围)
调用时带参数，则设置参数值(例如，plt.xlim([0，10])设置当前的X轴绘图范围为0到10)
~~~所有这些方法都是对创建前或最近创建的AxesSubplot起作用的。他们各自对应subplot对象上的两个方法，如ax.get_xlim和ax.set_xlim
'''


def testlabel():
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    ax.plot(np.random.randn(1000).cumsum())
    # 要改变x轴可图，使用set_xticks和set_xticklabels。前者告诉matplotlib要将刻度放在数据范围中的哪些位置，默认这些位置也是刻度标签。
    # 也可通过set_xticklabels将任何其他的值用作标签,rotation设置x可图标签倾斜度
    ticks = ax.set_xticks([0, 250, 500, 750, 1000])
    labels = ax.set_xticklabels(
        ['one', 'two', 'three', 'four', 'five'],
        rotation=30,
        fontsize='small')
    # 最后再用set_title设置一个标题
    ax.set_title('My first matplotlib plot')
    # 设置x轴名称
    ax.set_xlabel('Stages')
    # 轴的类有集合方法，可以批量设定绘图选项，如设定y轴
    props = {'title': 'My test matplotlib', 'ylabel': 'Ytest'}
    ax.set(**props)  # 字典型参数要进行参数释放
    plt.show()
# testlabel()


def testlegend():
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    ax.plot(np.random.randn(1000).cumsum(), 'k', label='one')
    ax.plot(np.random.randn(1000).cumsum(), 'k--', label='two')
    ax.plot(np.random.randn(1000).cumsum(), 'k.', label='three')
    # 调用ax.legend()或plt.legend()来自动创建图例，从图例中删除一个，可以在上面不传入label或label='nolegend'
    ax.legend(loc='best')  # 一般传入best即可，固定legend位置，可指定其他值，具体可查plt.legend?
    plt.show()
# testlegend()

# --------------------注解及在Subplot上绘图--------------
# 注解可以通过text、arrow和annotate函数进行添加。text可以将文本绘制在图表的指定坐标(x,y)，还可以加上一些自定义格式
# ax.text(x,y,'Helloworld!',family='monospace',fontsize=10)
# annotate使用,还可加参数(,textcoords='offset points',fontsize=8)
# ax[i].annotate(label,xy=(x,y),xytext=(x,y+50),arrowprops=dict(facecolor='black',headwidth=4,width=0.1,alpha=0.7),horizontalalignment='left',verticalalignment='top')


# 图形的绘制，常见突性的对象称为块，其中有些(如Rectangle和Circle)，可以在matplotlib.pyplot中找到，但完整集合位于matplotlib.patches
# 要在图表中添加一个图形，需要创建一个块对象shp,通过ax.add_patch(shp)将其添加到subplot中
def testgraph():
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    rect = plt.Rectangle((0.2, 0.75), 0.4, 0.15, color='k', alpha=0.3)
    circ = plt.Circle((0.7, 0.2), 0.15, color='b', alpha=0.3)
    pgon = plt.Polygon(
        [[0.15, 0.15], [0.35, 0.4], [0.2, 0.6]],
        color='g',
        alpha=0.5)
    ax.add_patch(rect)
    ax.add_patch(circ)
    ax.add_patch(pgon)
    # 注解
    ax.annotate(
        'Rectangle',
        xy=(0.6, 0.75),
        xytext=(0.7, 0.6),
        arrowprops=dict(facecolor='black',
                        headwidth=4,
                        width=0.1,
                        alpha=0.7))
    plt.show()
# testgraph()


# ------------图表保存到文件------------
# 使用savefig()，例如需要一张带有最小白边且分别率是400dpi的PNG图片
def savefigtest():
    plt.plot(np.random.randn(40), 'k--')
    # dpi（控制“每英⼨点数”分辨率）和bbox_inches（可以剪除当前图表周围的空⽩部分）
    plt.savefig('figpath.png', dpi=400, bbox_inches='tight')
    # 也可写入任何文件型的对象，比如BytesIO
    from io import BytesIO
    buffer = BytesIO()
    plt.savefig(buffer)
    plot_data = buffer.getvalue()
    '''
    savefig参数：fname--含有文件路径的字符串或Python的文件型对象。图像格式由文件扩展名推断得出
    dpi--图像分辨率(每英寸点数)。默认100
    facecolor,edgecolor--图像的背景色，默认'w'白色
    format--显示设置文件格式('png','pdf'等)
    bbox_inches--图表需要保存的部分。如果设置为tight，则尝试剪除图表周围的空白部分
    '''
# savefigtest()


# -------------matplotlib配置------------
'''
matplotlib自带一些配色方案，以及为生成出版质量的图片而设定的默认配置信息。幸运的是，几乎所有默认行为都能通过一组全局参数进行自定义。
他们可以管理图像大小、subplot边距、配色方案、字体大小、网格类型等。
一种Python编程方式配置系统的方法是使用rc方法。例如设置全局的图像默认大小为10*10
plt.rc('figure',figsize=(10,10))
rc函数的第一个参数是希望自定义的对象，如'figure'、'axes'、'xtick'、'ytick'、'grid'、'legend'等。其后可以跟上⼀系列的关键字参数。⼀个简单的办法是将这些选项写成⼀个字典：
font_options={'family':'monospace','weight':'bold','size':'10'}
plt.rc('font',**font_options)
要了解全部的自定义选项，请查阅matplotlib的配置文件matplotlibrc(位于matplotlib/mpl-data目录中)。
如果对该文件进行了自定义，并将其放在完全自己的.matplotlibrc目录中，则每次使用matplotlib时就会加载该文件。
'''

# --------------------------------------使用pandas和seaborn绘图----------------------
import pandas as pd
# pandas的大部分绘图方法都有一个可选的ax参数，它是一个subplot对象，能够在网格布局中灵活的处理subplot的位置。
# 线型图
'''
Series和DataFrame都有一个用于生成各类图标的plot方法。默认情况生成的是线型图。
'''


def testlinedraw():
    '''
    plot的参数：label--用于图例的标签；ax--要在其上进行绘制的matplotlib subplot对象。如果没有设置，则使用当前subplot；
    style--将要传给matplotlib的风格字符串(如'ko--');alpha--图标的填充不透明度；kind--可以是'line'、'bar'、'barh'、'kde'；
    logy--在y轴上使用对数标尺；use_index--将对象的索引用作刻度标签;rot--旋转刻度标签(0到360);xticks--用作X轴刻度的值；
    yticks--用作Y轴刻度的值；xlim、ylim--X轴、Y轴的界限；grid--显示轴网格线(默认打开)
    '''
    # Series对象的索引会被传给matplotlib，并用以绘制X轴。可以通过use_index=False禁用该功能。坐标轴可以通过xtick、ytick和xlim、ylim调节。
    s = pd.Series(np.random.randn(10).cumsum(), index=np.arange(0, 100, 10))
    s.plot()
    # DataFrame的plot方法会在一个subplot中为各列绘制一条线，并自动创建图例
    df = pd.DataFrame(
        np.random.randn(10, 4).cumsum(0),
        columns=['A', 'B', 'C', 'D'],
        index=np.arange(0, 100, 10))
    df.plot()
    # plot属性包含一批不同绘图类型的方法，例如df.plot()等价于df.plot.line()
    '''
    DataFrame除了有开头说的那些参数外，还有一些参数选项。
    subplots--将各个DataFrame列绘制到单独的subplot中；sharex--如果subplots=True，则共用同一个x轴，包括刻度和界限。
    sharey--同sharex；figsize--表示图像大小的元组；title--图像标题的字符串；legend--添加一个subplot图例(默认True);
    sort_columns--以字母表顺序绘制各列，默认使用当前列顺序
    '''
    plt.show()

# testlinedraw()


# 柱状图
# plot.bar()和plot.barh()分别绘制水平和垂直的柱状图。这是，Series和DataFrame的索引将被用作X(bar)或Y(barh)刻度
def testbarplot():
    # Series
    fig, axes = plt.subplots(2, 1)
    data = pd.Series(np.random.rand(16), index=list('abcdefghijklmnop'))
    # color='k'和alpha=0.7设定了图形的颜色为黑色，并使用部分填充透明度。
    data.plot.bar(ax=axes[0], color='k', alpha=0.7)
    data.plot.barh(ax=axes[1], color='k', alpha=0.7)

    # 如果是独立的bar
    plt.figure()
    plt.bar(x=data.index, height=data.values, color='green', align='center')

    # 对于DataFrame,柱状图会将每一行的值分为一组，并排显示
    df = pd.DataFrame(
        np.random.rand(6, 4),
        index=['one', 'two', 'three', 'four', 'five', 'six'],
        columns=pd.Index(['A', 'B', 'C', 'D'],
                         name='Genus'))  # name参数定义的是columns列的总名称
    print(df.columns.name)
    # 列的总名称用作图例的标题
    df.plot.bar()
    # 设置stacked=True 即可为DataFrame生成堆积柱状图，这样每行的值就会被堆积在一起
    df.plot.barh(stacked=True, alpha=0.5)
    plt.show()
# testbarplot()


def barexample():
    # 假设我们想要做⼀张堆积柱状图以展示每天各种聚会规模的数据点的百分⽐。根据⽇期和聚会规模创建⼀张交叉表
    tips = pd.read_csv('examples/tips.csv')
    '''
    交叉表：分类汇总统计表。行沿水平方向延伸(从一侧到另一侧)。比如某一行是物品--"手套"。列沿垂直方向延伸(上下)，比如某一列是美国。则这一行一列交叉的网格处应是同时满足行和列条件的记录汇总，比如在美国销售的手套数量。
    每行或每列表示满足某一个条件的记录汇总，比如所有国家卖出的手套汇总在"手套"行的末尾。
    右下角则是所有汇总，比如所有国家卖出的所有物品记录汇总。
    '''
    # 两个变量的交叉表(列联表)，是双向表。
    party_counts = pd.crosstab(tips['day'], tips['size'])
    print(party_counts)
    # index和columns上有整数索引，loc里的数字切片是标签切片，非位置切片，应包含末端
    # 1人、6人的规模太少，不统计
    party_counts = party_counts.loc[:, 2:5]
    # 然后进⾏规格化，使得各⾏的和为1，sum(1)表示axis=1的sum。axis=1，从上到下，每行执行。
    # print(party_counts.sum(1))
    party_pcts = party_counts.div(party_counts.sum(1), axis=0)
    party_pcts.plot.bar()
    # 于是可以看出聚会规模在周末会变大

    # 对于在绘制一个图形之前，需要进行合计的数据，使用seaborn可以减少工作量。
    # 使用seaborn来看每天的消费比例:消费比实际消费
    # seaborn已经自动修改了图形的美观度：默认调色板，图形背景和网格线的颜色。可以使用seaborn.set在不同的图形外观之间切换
    sns.set(style='whitegrid')
    tips['tip_pct'] = tips['tip'] / (tips['total_bill'] - tips['tip'])
    # 设定新的画布，否则会在前一画布上画
    plt.figure()
    # x是x轴标签；y是y轴标签；data是数据源，可以是一个DataFrame；因每个x值有多个观察值，故bar显示的是平均值；orient是作图方向
    # sns.barplot(x='tip_pct',y='day',data=tips,orient='h') # 柱状图上的黑线代表95%置信区间。
    # seaborn.barplot有颜色选项，可以通过一个值设置
    sns.barplot(
        x='tip_pct', y='day',
        hue='time', data=tips,
        orient='h')  # 柱状图上的黑线代表95%置信区间。
    plt.show()
# barexample()


# 直方图和密度图
'''
直方图是一种可以对值频率进行离散化显示的柱状图。数据点被拆分到离散的、间隔均匀的面元中，绘制的是各面元中数据点的数量。
'''


def testhist():
    # 同上，计算消费占消费额的百分比
    tips = pd.read_csv('examples/tips.csv')
    tips['tip_pct'] = tips['tip'] / (tips['total_bill'] - tips['tip'])
    tips['tip_pct'].plot.hist(bins=50)
    '''
    与直方图相关的就是密度图，它是通过计算"可能会产⽣观测数据的连续概率分布的估计"而产生的。
    ⼀般的过程是将该分布近似为⼀组核（即诸如正态分布之类的较为简单的分布）。因此，密度图也被称作KDE（Kernel    Density    Estimate，核密度估计）图。使⽤plot.kde和标准混合正态分布估计即可⽣成⼀张密度图
    '''
    plt.figure()
    tips['tip_pct'].plot.density()

    # 使用seaborn的displot方法绘制直方图和密度图更简单，还可以同时画出直⽅图和连续密度估计图。
    # 作为例⼦，考虑⼀个双峰分布，由两个不同的标准正态分布组成
    comp1 = np.random.normal(0, 1, size=200)
    comp2 = np.random.normal(10, 2, size=200)
    values = pd.Series(np.concatenate([comp1, comp2]))
    plt.figure()
    sns.distplot(values, bins=100, color='k')
    plt.show()
# testhist()


# 散布图或点图
# 点图或散布图是观察两个一维数据序列之间的关系的有效手段。
def dotplot():
    macro = pd.read_csv('examples/macrodata.csv')
    data = macro[['cpi', 'm1', 'tbilrate', 'unemp']]
    trans_data = np.log(data).diff().dropna()
    print(trans_data[-5:])
    # 使用seaborn的regplot方法，作一个散布图，并加上一条线型回归线
    sns.regplot('m1', 'unemp', data=trans_data)
    plt.title('Changes in log %s versus log %s' % ('m1', 'unemp'))

    # 在探索式数据分析工作中，同时观察一组变量的散布图是很有意义的，也被称为散布图矩阵。
    # pariplot函数，在对角线上放置每个变量的直方图或密度估计
    sns.pairplot(trans_data, diag_kind='kde', plot_kws={'alpha': 0.2})
    plt.show()
# dotplot()


# 分面网格和类型数据
'''
要是数据集有额外的分组维度呢？有多个分类变量的数据可视化的一种方法是使用小面网格。
'''


# 使用catplot函数
def testcatplot():
    # 同前面，计算消费占消费额的百分比
    tips = pd.read_csv('examples/tips.csv')
    tips['tip_pct'] = tips['tip'] / (tips['total_bill'] - tips['tip'])
    sns.catplot(
        x='day',
        y='tip_pct',
        hue='time',
        col='smoker',
        kind='bar',
        data=tips[tips.tip_pct < 1])

    # 除了在分面中用不同的颜色按时间分组，还可以通过给每个时间值添加一行来扩展分面网格
    sns.catplot(
        x='day',
        y='tip_pct',
        row='time',
        col='smoker',
        kind='bar',
        data=tips[tips.tip_pct < 1])

    # catplot支持其他的绘图类型，例如盒图(可以显示中位数，四分位数，和异常值)
    sns.catplot(
        x='tip_pct',
        y='day',
        kind='box',
        data=tips[tips.tip_pct < 0.5])
    plt.show()


testcatplot()
